<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<meta name="theme-color" content="#222"><meta name="generator" content="Hexo 6.3.0">

  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">



<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" integrity="sha256-Z1K5uhUaJXA7Ll0XrZ/0JhX4lAtZFpT6jkKrEDT0drU=" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.1.1/animate.min.css" integrity="sha256-PR7ttpcvz8qrF57fur/yAx1qXMFJeJFiA6pSzWi0OIE=" crossorigin="anonymous">

<script class="next-config" data-name="main" type="application/json">{"hostname":"example.com","root":"/","images":"/images","scheme":"Muse","darkmode":false,"version":"8.14.1","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12},"copycode":{"enable":false,"style":null},"bookmark":{"enable":false,"color":"#222","save":"auto"},"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"stickytabs":false,"motion":{"enable":true,"async":false,"transition":{"menu_item":"fadeInDown","post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"搜索...","empty":"没有找到任何搜索结果：${query}","hits_time":"找到 ${hits} 个搜索结果（用时 ${time} 毫秒）","hits":"找到 ${hits} 个搜索结果"},"path":"/search.xml","localsearch":{"enable":true,"trigger":"auto","top_n_per_article":-1,"unescape":false,"preload":false}}</script><script src="/js/config.js"></script>

    <meta name="description" content="一、集群的作用集群，即Redis Cluster，是Redis 3.0开始引入的分布式存储方案。 集群由多个节点(Node)组成，Redis的数据分布在这些节点中。 集群中的节点分为主节点和从节点： 只有主节点负责读写请求和集群信息的维护；从节点只进行主节点数据和状态信息的复制。 集群的作用，可以归纳为两点：  数据分区：数据分区(或称数据分片)是集群最核心的功能。  集群将数据分散到多个节点，一">
<meta property="og:type" content="article">
<meta property="og:title" content="Redis-集群">
<meta property="og:url" content="http://example.com/2021/12/16/Redis-%E9%9B%86%E7%BE%A4/index.html">
<meta property="og:site_name" content="JsyBlog">
<meta property="og:description" content="一、集群的作用集群，即Redis Cluster，是Redis 3.0开始引入的分布式存储方案。 集群由多个节点(Node)组成，Redis的数据分布在这些节点中。 集群中的节点分为主节点和从节点： 只有主节点负责读写请求和集群信息的维护；从节点只进行主节点数据和状态信息的复制。 集群的作用，可以归纳为两点：  数据分区：数据分区(或称数据分片)是集群最核心的功能。  集群将数据分散到多个节点，一">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="http://example.com/2021/12/16/Redis-%E9%9B%86%E7%BE%A4/hash-slot.png">
<meta property="og:image" content="http://example.com/2021/12/16/Redis-%E9%9B%86%E7%BE%A4/1174710-20181025213612837-648236990.png">
<meta property="og:image" content="http://example.com/2021/12/16/Redis-%E9%9B%86%E7%BE%A4/1174710-20181025213630192-1534783794.png">
<meta property="article:published_time" content="2021-12-16T06:17:42.000Z">
<meta property="article:modified_time" content="2022-03-16T18:52:40.106Z">
<meta property="article:author" content="SongyangJi">
<meta property="article:tag" content="NoSQL">
<meta property="article:tag" content="Redis">
<meta property="article:tag" content="分布式">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="http://example.com/2021/12/16/Redis-%E9%9B%86%E7%BE%A4/hash-slot.png">


<link rel="canonical" href="http://example.com/2021/12/16/Redis-%E9%9B%86%E7%BE%A4/">



<script class="next-config" data-name="page" type="application/json">{"sidebar":"","isHome":false,"isPost":true,"lang":"zh-CN","comments":true,"permalink":"http://example.com/2021/12/16/Redis-%E9%9B%86%E7%BE%A4/","path":"2021/12/16/Redis-集群/","title":"Redis-集群"}</script>

<script class="next-config" data-name="calendar" type="application/json">""</script>
<title>Redis-集群 | JsyBlog</title>
  








  <noscript>
    <link rel="stylesheet" href="/css/noscript.css">
  </noscript>
</head>

<body itemscope itemtype="http://schema.org/WebPage" class="use-motion">
  <div class="headband"></div>

  <main class="main">
    <div class="column">
      <header class="header" itemscope itemtype="http://schema.org/WPHeader"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏" role="button">
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <i class="logo-line"></i>
      <p class="site-title">JsyBlog</p>
      <i class="logo-line"></i>
    </a>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger" aria-label="搜索" role="button">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>



<nav class="site-nav">
  <ul class="main-menu menu"><li class="menu-item menu-item-home"><a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a></li><li class="menu-item menu-item-tags"><a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a></li><li class="menu-item menu-item-categories"><a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a></li><li class="menu-item menu-item-archives"><a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a></li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup"><div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off" maxlength="80"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close" role="button">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div class="search-result-container no-result">
  <div class="search-result-icon">
    <i class="fa fa-spinner fa-pulse fa-5x"></i>
  </div>
</div>

    </div>
  </div>

</header>
        
  
  <aside class="sidebar">

    <div class="sidebar-inner sidebar-nav-active sidebar-toc-active">
      <ul class="sidebar-nav">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <div class="sidebar-panel-container">
        <!--noindex-->
        <div class="post-toc-wrap sidebar-panel">
            <div class="post-toc animated"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%B8%80%E3%80%81%E9%9B%86%E7%BE%A4%E7%9A%84%E4%BD%9C%E7%94%A8"><span class="nav-number">1.</span> <span class="nav-text">一、集群的作用</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%BA%8C%E3%80%81%E9%9B%86%E7%BE%A4%E7%9A%84%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86"><span class="nav-number">2.</span> <span class="nav-text">二、集群的基本原理</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%B8%89%E3%80%81%E5%AE%9E%E8%B7%B5%E9%A1%BB%E7%9F%A5"><span class="nav-number">3.</span> <span class="nav-text">三、实践须知</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#1-%E9%9B%86%E7%BE%A4%E4%BC%B8%E7%BC%A9"><span class="nav-number">3.1.</span> <span class="nav-text">1. 集群伸缩</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%A2%9E%E5%8A%A0%E8%8A%82%E7%82%B9"><span class="nav-number">3.1.1.</span> <span class="nav-text">增加节点</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%87%8F%E5%B0%91%E8%8A%82%E7%82%B9"><span class="nav-number">3.1.2.</span> <span class="nav-text">减少节点</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#ASK%E9%94%99%E8%AF%AF"><span class="nav-number">3.1.3.</span> <span class="nav-text">ASK错误</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#2-%E6%95%85%E9%9A%9C%E8%BD%AC%E7%A7%BB"><span class="nav-number">3.2.</span> <span class="nav-text">2. 故障转移</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#3-%E9%9B%86%E7%BE%A4%E7%9A%84%E9%99%90%E5%88%B6%E5%8F%8A%E5%BA%94%E5%AF%B9%E6%96%B9%E6%B3%95"><span class="nav-number">3.3.</span> <span class="nav-text">3. 集群的限制及应对方法</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#4-Hash-Tag"><span class="nav-number">3.4.</span> <span class="nav-text">4. Hash Tag</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#5-%E5%8F%82%E6%95%B0%E4%BC%98%E5%8C%96"><span class="nav-number">3.5.</span> <span class="nav-text">5. 参数优化</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#cluster-node-timeout"><span class="nav-number">3.5.1.</span> <span class="nav-text">cluster_node_timeout</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#cluster-require-full-coverage"><span class="nav-number">3.5.2.</span> <span class="nav-text">cluster-require-full-coverage</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#6-redis-trib-rb"><span class="nav-number">3.6.</span> <span class="nav-text">6. redis-trib.rb</span></a></li></ol></li></ol></div>
        </div>
        <!--/noindex-->

        <div class="site-overview-wrap sidebar-panel">
          <div class="site-author animated" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">SongyangJi</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap animated">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
        <a href="/archives/">
          <span class="site-state-item-count">251</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
          <a href="/categories/">
        <span class="site-state-item-count">45</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
          <a href="/tags/">
        <span class="site-state-item-count">109</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>

        </div>
      </div>
    </div>

    
  </aside>


    </div>

    <div class="main-inner post posts-expand">


  


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/12/16/Redis-%E9%9B%86%E7%BE%A4/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="Redis-集群 | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          Redis-集群
        </h1>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2021-12-16 14:17:42" itemprop="dateCreated datePublished" datetime="2021-12-16T14:17:42+08:00">2021-12-16</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-03-17 02:52:40" itemprop="dateModified" datetime="2022-03-17T02:52:40+08:00">2022-03-17</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/Redis/" itemprop="url" rel="index"><span itemprop="name">Redis</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
        <h1 id="一、集群的作用"><a href="#一、集群的作用" class="headerlink" title="一、集群的作用"></a>一、集群的作用</h1><p>集群，即Redis Cluster，是Redis 3.0开始引入的分布式存储方案。</p>
<p>集群由多个节点(Node)组成，Redis的数据分布在这些节点中。</p>
<p><strong>集群中的节点分为主节点和从节点</strong>：</p>
<p><strong>只有主节点负责读写请求和集群信息的维护；从节点只进行主节点数据和状态信息的复制</strong>。</p>
<p>集群的作用，可以归纳为两点：</p>
<ol>
<li><strong>数据分区</strong>：数据分区(或称数据分片)是集群最核心的功能。</li>
</ol>
<p>集群将数据分散到多个节点，一方面突破了Redis单机内存大小的限制，<strong>存储容量大大增加</strong>；</p>
<p>另一方面每个主节点都可以对外提供读服务和写服务，<strong>大大提高了集群的响应能力</strong>。</p>
<blockquote>
<p>Redis单机内存大小受限问题，在介绍持久化和主从复制时都有提及；例如，如果单机内存太大，bgsave和bgrewriteaof的fork操作可能导致主进程阻塞，主从环境下主机切换时可能导致从节点长时间无法提供服务，全量复制阶段主节点的复制缓冲区可能溢出……。</p>
</blockquote>
<ol start="2">
<li><strong>高可用</strong>：<strong>集群支持主从复制和主节点的自动故障转移</strong>（与哨兵类似）；当<strong>任一节点发生故障时，集群仍然可以对外提供服务</strong>。</li>
</ol>
<h1 id="二、集群的基本原理"><a href="#二、集群的基本原理" class="headerlink" title="二、集群的基本原理"></a>二、集群的基本原理</h1><p>数据分区有顺序分区、哈希分区等，其中哈希分区由于其天然的随机性，使用广泛；集群的分区方案便是哈希分区的一种。</p>
<p>哈希分区的基本思路是：对数据的特征值（如key）进行哈希，然后根据哈希值决定数据落在哪个节点。常见的哈希分区包括：哈希取余分区、一致性哈希分区、带虚拟节点的一致性哈希分区等。</p>
<p>衡量数据分区方法好坏的标准有很多，其中比较重要的两个因素是(1)数据分布是否均匀(2)增加或删减节点对数据分布的影响。由于哈希的随机性，哈希分区基本可以保证数据分布均匀；因此在比较哈希分区方案时，重点要看增减节点对数据分布的影响。</p>
<p>（1）<strong>哈希取余分区</strong></p>
<p>哈希取余分区思路非常简单：计算key的hash值，然后对节点数量进行取余，从而决定数据映射到哪个节点上。该方案最大的问题是，当新增或删减节点时，节点数量发生变化，系统中所有的数据都需要重新计算映射关系，引发大规模数据迁移。</p>
<p>（2）<strong>一致性哈希分区</strong></p>
<p>一致性哈希算法将整个哈希值空间组织成一个虚拟的圆环，如下图所示，范围为0-2^32-1；对于每个数据，根据key计算hash值，确定数据在环上的位置，然后从此位置沿环顺时针行走，找到的第一台服务器就是其应该映射到的服务器。</p>
<p>与哈希取余分区相比，一致性哈希分区将增减节点的影响限制在相邻节点。以上图为例，如果在node1和node2之间增加node5，则只有node2中的一部分数据会迁移到node5；如果去掉node2，则原node2中的数据只会迁移到node4中，只有node4会受影响。</p>
<p>一致性哈希分区的主要问题在于，当节点数量较少时，增加或删减节点，对单个节点的影响可能很大，造成数据的严重不平衡。还是以上图为例，如果去掉node2，node4中的数据由总数据的1&#x2F;4左右变为1&#x2F;2左右，与其他节点相比负载过高。此时可以将一个真实节点映射成多个虚拟节点，然后将多个虚拟节点分布到哈希环上。</p>
<p>（3）<strong>哈希槽</strong></p>
<p>该方案在一致性哈希分区的基础上，引入了虚拟节点的概念。<strong>Redis集群使用的便是该方案，其中的虚拟节点称为槽（slot）。</strong>槽是介于数据和实际节点之间的虚拟概念；每个实际节点包含一定数量的槽，每个槽包含哈希值在一定范围内的数据。引入槽以后，数据的映射关系由<strong>数据hash-&gt;实际节点</strong>，变成了<strong>数据hash-&gt;槽-&gt;实际节点</strong>。</p>
<p><strong>在使用了槽的一致性哈希分区中，槽是数据管理和迁移的基本单位。槽解耦了数据和实际节点之间的关系，增加或删除节点对系统的影响很小。</strong>仍以上图为例，系统中有4个实际节点，假设为其分配16个槽(0-15)； 槽0-3位于node1，4-7位于node2，以此类推。如果此时删除node2，只需要将槽4-7重新分配即可，例如槽4-5分配给node1，槽6分配给node3，槽7分配给node4；可以看出删除node2后，数据在其他节点的分布仍然较为均衡。</p>
<p>槽的数量一般远小于2^32，远大于实际节点的数量；在Redis集群中，槽的数量为<strong>16384</strong>。</p>
<p>下面这张图很好的总结了Redis集群将数据映射到实际节点的过程：</p>
<p><img src="/2021/12/16/Redis-%E9%9B%86%E7%BE%A4/hash-slot.png"></p>
<p>（1）Redis对数据的特征值（一般是key）计算哈希值，使用的算法是CRC16。</p>
<p>（2）根据哈希值，计算数据属于哪个槽。</p>
<p>（3）根据槽与节点的映射关系，计算数据属于哪个节点。</p>
<h1 id="三、实践须知"><a href="#三、实践须知" class="headerlink" title="三、实践须知"></a>三、实践须知</h1><h2 id="1-集群伸缩"><a href="#1-集群伸缩" class="headerlink" title="1. 集群伸缩"></a>1. 集群伸缩</h2><p>实践中常常需要对集群进行伸缩，如访问量增大时的扩容操作。Redis集群可以在不影响对外服务的情况下实现伸缩；<strong>伸缩的核心是槽迁移：修改槽与节点的对应关系，实现槽(即数据)在节点之间的移动。</strong></p>
<p>例如，如果槽均匀分布在集群的3个节点中，此时增加一个节点，则需要从3个节点中分别拿出一部分槽给新节点，从而实现槽在4个节点中的均匀分布。</p>
<h3 id="增加节点"><a href="#增加节点" class="headerlink" title="增加节点"></a>增加节点</h3><p>假设要增加7003和8003节点，其中8003是7003的从节点；步骤如下：</p>
<p>（1）<strong>启动节点</strong>：方法参见集群搭建</p>
<p>（2）<strong>节点握手</strong>：可以使用<code>cluster meet</code>命令，但在生产环境中建议使用redis-trib.rb的add-node工具，其原理也是cluster meet，但它会先检查新节点是否已加入其它集群或者存在数据，避免加入到集群后带来混乱。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">redis-trib.rb ``add``-node 192.168.72.128:7003 192.168.72.128 7000``redis-trib.rb ``add``-node 192.168.72.128:8003 192.168.72.128 7000</span><br></pre></td></tr></table></figure>

<p>（3）<strong>迁移槽</strong>：推荐使用redis-trib.rb的reshard工具实现。reshard自动化程度很高，只需要输入**redis-trib.rb reshard ip:port (ip和port可以是集群中的任一节点)**，然后按照提示输入以下信息，槽迁移会自动完成：</p>
<ul>
<li>待迁移的槽数量：16384个槽均分给4个节点，每个节点4096个槽，因此待迁移槽数量为4096</li>
<li>目标节点id：7003节点的id</li>
<li>源节点的id：7000&#x2F;7001&#x2F;7002节点的id</li>
</ul>
<p>（4）<strong>指定主从关系</strong>：方法参见集群搭建</p>
<h3 id="减少节点"><a href="#减少节点" class="headerlink" title="减少节点"></a>减少节点</h3><p>假设要下线7000&#x2F;8000节点，可以分为两步：</p>
<p>（1）<strong>迁移槽</strong>：使用reshard将7000节点中的槽均匀迁移到7001&#x2F;7002&#x2F;7003节点</p>
<p>（2）<strong>下线节点</strong>：使用redis-trib.rb del-node工具；应先下线从节点再下线主节点，因为若主节点先下线，从节点会被指向其他主节点，造成不必要的全量复制。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">redis-trib.rb del-node 192.168.72.128:7001 &#123;节点8000的id&#125;</span><br><span class="line">redis-trib.rb del-node 192.168.72.128:7001 &#123;节点7000的id&#125;</span><br></pre></td></tr></table></figure>



<h3 id="ASK错误"><a href="#ASK错误" class="headerlink" title="ASK错误"></a>ASK错误</h3><p>集群伸缩的核心是槽迁移。在槽迁移过程中，如果客户端向源节点发送命令，源节点执行流程如下：</p>
<p><img src="/2021/12/16/Redis-%E9%9B%86%E7%BE%A4/1174710-20181025213612837-648236990.png" alt="img"></p>
<p>图片来源：《Redis设计与实现》</p>
<p>客户端收到ASK错误后，从中读取目标节点的地址信息，并向目标节点重新发送请求，就像收到MOVED错误时一样。但是二者有很大区别：ASK错误说明数据正在迁移，不知道何时迁移完成，因此重定向是临时的，SMART客户端不会刷新slots缓存；MOVED错误重定向则是(相对)永久的，SMART客户端会刷新slots缓存。</p>
<h2 id="2-故障转移"><a href="#2-故障转移" class="headerlink" title="2. 故障转移"></a>2. 故障转移</h2><p>在 《哨兵》一文中，介绍了哨兵实现故障发现和故障转移的原理。</p>
<p>虽然细节上有很大不同，但集群的实现与哨兵思路类似：<strong>通过定时任务发送PING消息检测其他节点状态；节点下线分为主观下线和客观下线；客观下线后选取从节点进行故障转移</strong>。</p>
<p><strong>与哨兵一样，集群只实现了主节点的故障转移</strong>；</p>
<p><strong>从节点故障时只会被下线，不会进行故障转移</strong>。因此，使用集群时，应谨慎使用读写分离技术，因为从节点故障会导致读服务不可用，可用性变差。</p>
<p>这里不再详细介绍故障转移的细节，只对重要事项进行说明：</p>
<p><strong>节点数量：</strong>在故障转移阶段，需要由主节点投票选出哪个从节点成为新的主节点；从节点选举胜出需要的票数为N&#x2F;2+1；其中N为主节点数量(包括故障主节点)，但故障主节点实际上不能投票。因此为了能够在故障发生时顺利选出从节点，集群中至少需要3个主节点(且部署在不同的物理机上)。</p>
<p><strong>故障转移时间：</strong>从主节点故障发生到完成转移，所需要的时间主要消耗在主观下线识别、主观下线传播、选举延迟等几个环节；具体时间与参数cluster-node-timeout有关，一般来说：</p>
<p>故障转移时间(毫秒) ≤ 1.5 * cluster-node-timeout + 1000</p>
<p>cluster-node-timeout的默认值为15000ms(15s)，因此故障转移时间会在20s量级。</p>
<h2 id="3-集群的限制及应对方法"><a href="#3-集群的限制及应对方法" class="headerlink" title="3. 集群的限制及应对方法"></a>3. 集群的限制及应对方法</h2><p>由于集群中的数据分布在不同节点中，导致一些功能受限，包括：</p>
<p>（1）key批量操作受限：例如mget、mset操作，只有当操作的key都位于一个槽时，才能进行。针对该问题，一种思路是在客户端记录槽与key的信息，每次针对特定槽执行mget&#x2F;mset；另外一种思路是使用Hash Tag，将在下一小节介绍。</p>
<p>（2）keys&#x2F;flushall等操作：keys&#x2F;flushall等操作可以在任一节点执行，但是结果只针对当前节点，例如keys操作只返回当前节点的所有键。针对该问题，可以在客户端使用cluster nodes获取所有节点信息，并对其中的所有主节点执行keys&#x2F;flushall等操作。</p>
<p>（3）事务&#x2F;Lua脚本：集群支持事务及Lua脚本，但前提条件是所涉及的key必须在同一个节点。Hash Tag可以解决该问题。</p>
<p>（4）数据库：单机Redis节点可以支持16个数据库，集群模式下只支持一个，即db0。</p>
<p>（5）复制结构：只支持一层复制结构，不支持嵌套。</p>
<h2 id="4-Hash-Tag"><a href="#4-Hash-Tag" class="headerlink" title="4. Hash Tag"></a>4. Hash Tag</h2><p>Hash Tag原理是：<strong>当一个key包含 {}的时候，不对整个key做hash，而仅对 {}包括的字符串做hash。</strong></p>
<p><strong>Hash Tag可以让不同的key拥有相同的hash值，从而分配在同一个槽里</strong>；</p>
<p>这样针对不同key的批量操作(mget&#x2F;mset等)，以及事务、Lua脚本等都可以支持。不过Hash Tag可能会带来数据分配不均的问题，这时需要：(1)调整不同节点中槽的数量，使数据分布尽量均匀；(2)避免对热点数据使用Hash Tag，导致请求分布不均。</p>
<p>下面是使用Hash Tag的一个例子；通过对product加Hash Tag，可以将所有产品信息放到同一个槽中，便于操作。</p>
<p><img src="/2021/12/16/Redis-%E9%9B%86%E7%BE%A4/1174710-20181025213630192-1534783794.png" alt="img"></p>
<h2 id="5-参数优化"><a href="#5-参数优化" class="headerlink" title="5. 参数优化"></a>5. 参数优化</h2><h3 id="cluster-node-timeout"><a href="#cluster-node-timeout" class="headerlink" title="cluster_node_timeout"></a>cluster_node_timeout</h3><p>cluster_node_timeout参数在前面已经初步介绍；它的默认值是15s，影响包括：</p>
<p>（1）影响PING消息接收节点的选择：值越大对延迟容忍度越高，选择的接收节点越少，可以降低带宽，但会降低收敛速度；应根据带宽情况和应用要求进行调整。</p>
<p>（2）影响故障转移的判定和时间：值越大，越不容易误判，但完成转移消耗时间越长；应根据网络状况和应用要求进行调整。</p>
<h3 id="cluster-require-full-coverage"><a href="#cluster-require-full-coverage" class="headerlink" title="cluster-require-full-coverage"></a>cluster-require-full-coverage</h3><p>前面提到，只有当16384个槽全部分配完毕时，集群才能上线。这样做是为了保证集群的完整性，但同时也带来了新的问题：当主节点发生故障而故障转移尚未完成，原主节点中的槽不在任何节点中，此时会集群处于下线状态，无法响应客户端的请求。</p>
<p>cluster-require-full-coverage参数可以改变这一设定：如果设置为no，则当槽没有完全分配时，集群仍可以上线。参数默认值为yes，如果应用对可用性要求较高，可以修改为no，但需要自己保证槽全部分配。</p>
<h2 id="6-redis-trib-rb"><a href="#6-redis-trib-rb" class="headerlink" title="6. redis-trib.rb"></a>6. redis-trib.rb</h2><p>redis-trib.rb提供了众多实用工具：创建集群、增减节点、槽迁移、检查完整性、数据重新平衡等；通过help命令可以查看详细信息。在实践中如果能使用redis-trib.rb工具则尽量使用，不但方便快捷，还可以大大降低出错概率。</p>

    </div>

    
    
    

    <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/NoSQL/" rel="tag"># NoSQL</a>
              <a href="/tags/Redis/" rel="tag"># Redis</a>
              <a href="/tags/%E5%88%86%E5%B8%83%E5%BC%8F/" rel="tag"># 分布式</a>
          </div>

        

          <div class="post-nav">
            <div class="post-nav-item">
                <a href="/2021/12/15/CopyOnWriteArrayList%E5%92%8CCopyOnWriteArraySet/" rel="prev" title="CopyOnWriteArrayList和CopyOnWriteArraySet">
                  <i class="fa fa-chevron-left"></i> CopyOnWriteArrayList和CopyOnWriteArraySet
                </a>
            </div>
            <div class="post-nav-item">
                <a href="/2021/12/16/SkipList/" rel="next" title="SkipList">
                  SkipList <i class="fa fa-chevron-right"></i>
                </a>
            </div>
          </div>
    </footer>
  </article>
</div>






</div>
  </main>

  <footer class="footer">
    <div class="footer-inner">


<div class="copyright">
  &copy; 
  <span itemprop="copyrightYear">2023</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">SongyangJi</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.js.org/muse/" rel="noopener" target="_blank">NexT.Muse</a> 强力驱动
  </div>

    </div>
  </footer>

  
  <div class="toggle sidebar-toggle" role="button">
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
  </div>
  <div class="sidebar-dimmer"></div>
  <div class="back-to-top" role="button" aria-label="返回顶部">
    <i class="fa fa-arrow-up fa-lg"></i>
    <span>0%</span>
  </div>

<noscript>
  <div class="noscript-warning">Theme NexT works best with JavaScript enabled</div>
</noscript>


  
  <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js" integrity="sha256-XL2inqUJaslATFnHdJOi9GfQ60on8Wx1C2H8DYiN1xY=" crossorigin="anonymous"></script>
<script src="/js/comments.js"></script><script src="/js/utils.js"></script><script src="/js/motion.js"></script><script src="/js/schemes/muse.js"></script><script src="/js/next-boot.js"></script>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/hexo-generator-searchdb/1.4.1/search.js" integrity="sha256-1kfA5uHPf65M5cphT2dvymhkuyHPQp5A53EGZOnOLmc=" crossorigin="anonymous"></script>
<script src="/js/third-party/search/local-search.js"></script>





  





</body>
</html>
